type VTypeString = 'number' | 'string' | 'array' | 'object' | 'boolean'
type VType = number | string | VType[] | object | boolean | undefined
type SchemaType = VTypeString | VTypeString[]
/**错误信息模板字符串 */
type ErrorTemplate<P, T = Context<P>> = string | ((param: T) => string)
/**上下文 */
export type Context<P> = StructContext<P> | ParamContext<P> | AttrContext<P>
/**结构上下文 */
export interface StructContext<P> {
  method: string
  schema: Schema<P>
  value: VType
  param: P
  key?: string | number
  vri?: Vri<P>
}
/**参数上下文 */
export interface ParamContext<P> {
  method: string
  schema: any
  value: any
  param: P
  key?: string | number
  vri?: Vri<P>
}
/**属性上下文 */
export interface AttrContext<P> {
  method: string
  schema: Schema<P>
  value: { [x: string]: any }
  param: P
  parentSchema: Schema<P>
  vri?: Vri<P>
}
/**自定义参数 */
export interface Param {}

export interface AttrSchema<P> {
  [k: string]: Schema<P>
}

interface Result<P> {
  /**是否通过验证 */
  adopt: boolean
  /**接受验证的值 */
  value: VType
  /**错误信息 */
  error?: ErrorMessage<P>
}

export class VriError extends Error {
  constructor(msg: string) {
    super(msg)
  }
  static isVerError(error: Error) {
    return error instanceof VriError
  }
}

export interface Schema<P> {
  [k: string]: any
  /**异 */
  no?: Schema<P>
  /**或 */
  or?: Schema<P>
  /**与 */
  and?: Schema<P>
  /**数据类型 */
  type?: SchemaType
  /**子属性 */
  attr?: AttrSchema<P>
  /**元素 */
  // item?: Schema
  /**必选的属性 */
  must?: boolean
  /**最大 */
  max?: number
  /**最小 */
  min?: number
  /**条件计数 */
  // count?: SchemaCount
  /**大于 */
  gt?: number
  /**小于 */
  lt?: number
  /**等于 */
  equal?: SchemaType
  /**包含字母（忽略大小写） */
  ContainsLetters?: boolean
  /**包含数字 */
  includeNumber?: boolean
  /**包含符号 */
  includeSymbol?: boolean
  /**匹配正则 */
  regEx?: RegExp
  /**不匹配正则 */
  noRegEx?: RegExp
  /**自定义验证 */
  verifier?: (value: any, context: Context<P>) => boolean | Result<P>
  /**默认值 */
  default?: SchemaType | ((context: Context<P>) => SchemaType)
  /**保留属性 */
  retain?: adoptParam<Context<P>>
  /**重命名 */
  as?: string
  custom?: (context: ParamContext<P>) => Result<P>
  /**预处理 */
  pretreat?: (value: any, context: Context<P>) => any
  /**映射 */
  map: (value: any, context: Context<P>) => any
  /**过滤 */
  filter: (value: any, context: Context<P>) => boolean
}

const Modifications = new Set([
  'default',
  // 'countEqual',
  // 'countMax',
  // 'countMin',
  // 'countBetween',
  'must',
  'error',
  'retain',
  'as',
])

function format<P>(context: Context<P>) {
  const { method, schema } = context
  const schemaParam = schema[method]
  const met = verifiers[method]
  if (met === undefined) {
    throw new VriError(
      `There is no ${method} verification Methods, Check method spelling or add this method to verifiers.`
    )
  }
  const rtn: Result<P> = met({ ...context, schema: schemaParam })
  return rtn
}

const verifiers = {
  /**与 */
  and<P>(context: StructContext<P>): Result<P> {
    let { schema, value } = context
    let rtn: Result<P>
    for (const k in schema) {
      if (Modifications.has(k)) continue
      rtn = format<P>({ ...context, method: k, value })
      if (!rtn.adopt) {
        return rtn
      }
      if (value !== rtn.value) value = rtn.value
    }
    return {
      adopt: true,
      value,
    }
  },
  /**属性 */
  attr<P>(context: AttrContext<P>): Result<P> {
    const { value, schema, parentSchema } = context
    const nv: any = {}
    for (const key in schema) {
      const v = value[key]
      delete value[key]
      const sp = schema[key]
      if (typeof sp !== 'object')
        throw new VriError(`The schema of property ${key} is not an object`)
      // 处理值不存在
      if (v === undefined || v === '') {
        if (sp.must) {
          return {
            adopt: false,
            error: new ErrorMessage({
              ...context,
              method: 'must',
              key,
            }),
            value: v,
          }
        } else if (sp.default) {
          if (typeof sp.default === 'function') {
            nv[key] = sp.default({ ...context, key: key })
          } else {
            nv[key] = sp.default
          }
        } else continue
      }
      const rtn = verifiers.and({
        ...context,
        schema: sp,
        value: v,
        key,
      })
      if (!rtn.adopt) return rtn
      if (v !== rtn.value) {
        if (sp.as) nv[sp.as] = rtn.value
        else nv[key] = rtn.value
      } else {
        if (sp.as) nv[sp.as] = v
        else nv[key] = v
      }
    }
    if (parentSchema && parentSchema.retain) {
      const { retain } = parentSchema
      for (const key in value) {
        const v = value[key]
        //@ts-ignore
        const isr = adopt(retain, v, context)
        if (isr) nv[key] = v
      }
    }
    return {
      adopt: true,
      value: nv,
    }
  },
  /**类型 */
  type<P>(context: ParamContext<P>): Result<P> {
    const { value, schema } = context
    const t = typeof value
    if (schema instanceof Array) {
      if (schema.includes(t)) {
        return {
          value: value,
          adopt: true,
        }
      }
      return {
        adopt: false,
        value: value,
        error: new ErrorMessage(context),
      }
    }
    if (t === 'string' && schema === 'number') {
      const rn = Number(value)
      if (isNaN(rn)) {
        return {
          adopt: false,
          value,
          error: new ErrorMessage(context),
        }
      }
      return {
        value: rn,
        adopt: true,
      }
    }
    if (schema === 'array') {
      if (Array.isArray(value)) {
        return {
          adopt: true,
          value,
        }
      } else {
        return {
          adopt: false,
          value,
          error: new ErrorMessage(context),
        }
      }
    }
    if (schema === t) {
      return {
        value,
        adopt: true,
      }
    } else {
      return {
        adopt: false,
        value,
        error: new ErrorMessage(context),
      }
    }
  },
  /**元素 */
  item<P>(context: ParamContext<P>): Result<P> {
    const { value } = context
    for (let index = 0; index < value.length; index++) {
      const e = value[index]
      const rt = verifiers.and({ ...context, value: e, key: index })
      if (!rt.adopt) return rt
    }
    return {
      adopt: true,
      value,
    }
  },
  /**预处理 */
  pretreat<P>(context: ParamContext<P>): Result<P> {
    return {
      adopt: true,
      value: context.schema(context.value),
    }
  },
  /**自定义 */
  custom<P>(context: ParamContext<P>): Result<P> {
    return context.schema(context)
  },
  /**映射 */
  map<P>(context: ParamContext<P>): Result<P> {
    const { value, schema } = context
    const nv = []
    for (let i = 0; i < value.length; i++) {
      const rt = schema(value[i], i, value)
      if (rt !== undefined) nv.push(rt)
    }
    return {
      adopt: true,
      value: nv,
    }
  },
  /**过滤 */
  filter<P>(context: ParamContext<P>): Result<P> {
    const { value, schema } = context
    const nv = []
    for (let i = 0; i < value.length; i++) {
      const rt = schema(value[i], i, value)
      if (rt) nv.push(value[i])
    }
    return {
      adopt: true,
      value: nv,
    }
  },
}

type adoptParam<T = any> =
  | RegExp
  | ((value: any, param: T) => boolean | void)
  | { inclueds: Function }

function adopt<T>(met: adoptParam<T>, value: any, param: T) {
  if (typeof met === 'function') {
    return met(value, param)
  }
  if (met instanceof RegExp) {
    return met.test(value)
  }
  //@ts-ignore
  return met.inclueds(value)
}

export class ErrorMessage<P> {
  template?: ErrorTemplate<P>
  context: Context<P>
  constructor(context: Context<P>) {
    this.context = context
  }
  toString(template?: ErrorTemplate<P>) {
    const { context } = this
    const { vri } = context
    // 优先使用实参模板
    if (!template) {
      // 使用局部模板
      if (this.template) template = this.template
      // 使用实例模板
      else if (vri && vri.errorTemplates && vri.errorTemplates[context.method])
        template = vri.errorTemplates[context.method]
      // 使用全局模板
      else template = errorTemplates[context.method]
    }
    if (!template) return ''
    //@ts-ignore
    if (context.key) {
      let map
      if (vri && vri.keyMap) map = vri.keyMap
      else map = Vri.keyMap
      //@ts-ignore
      if (map) context.key = Vri.keyMap(context.key, context)
    }
    if (typeof template === 'function') return template(context)
    return template.replace(/\[(.*?)\]/g, (a, b) => {
      const s = context[b]
      return s ? s + '' : ''
    })
  }
}

export function encapVerifier(callBack: Function) {
  return function (context: Context<any>) {
    const { schema, value } = context
    const bl = callBack(value, schema, context)
    if (bl) {
      return {
        adopt: true,
        value,
      }
    } else {
      return {
        adopt: false,
        value,
        error: new ErrorMessage(context),
      }
    }
  }
}

const vs = {
  /**最大 */
  max(v: any, p: Number) {
    if (typeof v.length === 'number') return v.length <= p
    return v <= p
  },
  /**最小 */
  min(v: any, p: Number) {
    if (typeof v.length === 'number') return v.length >= p
    return v >= p
  },
  /**大于 */
  gt(v: any, p: Number) {
    return v > p
  },
  /**小于 */
  lt(v: any, p: Number) {
    return v < p
  },
  /**等于 */
  equal(v: any, p: any) {
    return v == p
  },
  /**包含字母（忽略大小写） */
  ContainsLetters(v: any) {
    return /[a-z]/i.test(v)
  },
  /**包含数字 */
  includeNumber(v: any) {
    return /[0-9]/.test(v)
  },
  /**包含符号 */
  includeSymbol(v: any) {
    return /[`\[\];\'\,\.\/~\{\}:"<>\?\!@#$%^&\*\(\)_+\-=|\\]/.test(v)
  },
  /**匹配正则 */
  regEx(v: any, p: RegExp) {
    return p.test(v)
  },
  /**不匹配正则 */
  noRegEx(v: any, p: RegExp) {
    return !p.test(v)
  },
  /**自定义 */
  verifier(v: any, p: Function, context: Context<any>) {
    return p(v, context)
  },
}

for (const key in vs) {
  const callBack = vs[key]
  verifiers[key] = encapVerifier(callBack)
}

export const errorTemplates: { [x: string]: ErrorTemplate<any> } = {}

/**Vri验证 */
export class Vri<P = Param> {
  schema: Schema<P>
  errorTemplates?: { [x: string]: ErrorTemplate<P> }
  keyMap?<P>(key: string, context: Context<P>): string
  static keyMap?<P>(key: string, context: Context<P>): string
  constructor(schema: Schema<P>, errorTemplates?: { [x: string]: ErrorTemplate<P> }) {
    this.schema = schema
    this.errorTemplates = errorTemplates
  }
  /**
   *验证数据
   * @param value 验证的数据
   * @param param 传递的参数
   * @returns {Result}
   */
  verifies(value: any, param: P): Result<P> {
    return verifiers.and<P>({
      schema: this.schema,
      value,
      param,
      method: 'and',
      vri: this,
    })
  }
}
/**立即验证 */
export function verifies<P>(schema: Schema<P>, value: any, param: P) {
  return verifiers.and<P>({ value, param, method: 'and', schema })
}
